<?php
/**
 * Copyright 2012  SURFfoundation
 * 
 * This file is part of ESCAPE.
 * 
 * ESCAPE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * ESCAPE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ESCAPE.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * For more information:
 * http://escapesurf.wordpress.com/
 * http://www.surffoundation.nl/
 * 
 * Contact: d.vierkant@utwente.nl
 * 
 * @license http://www.gnu.org/licenses/gpl.html GNU GPLv3
 */
?>
<?php
/**
 * Triple store
 * 
 * Triple store abstraction layer. 
 */
abstract class triplestore_TripleStore
{
	// the cURL instance, preserved to allow HTTP keep-alive sessions
	protected $curlHandle = null;
	
	// the SPARQL end point URL
	protected $sparqlEndpointUrl = null;
	
	protected $username = null;
	protected $password = null;
	
	// application/sparql-results+json is the most efficient format for select and ask queries, alternatively application/sparql-results+xml can be used (currently only ask boolean XML resultsets are supported)
	protected $sparqlSelectFormat = 'application/sparql-results+json';
	protected $sparqlConstructFormat = 'application/rdf+xml';
	protected $sparqlAskFormat = 'application/sparql-results+xml';
	protected $sparqlDescribeFormat = 'application/rdf+xml';
	
	// additional parameters to pass when querying the SPARQL end point
	protected $sparqlSelectAdditionalParameters = array();
	protected $sparqlConstructAdditionalParameters = array();
	protected $sparqlAskAdditionalParameters = array();
	protected $sparqlDescribeAdditionalParameters = array();
	
	/**
	 * Get a triple store implementation by class name.
	 * @param string $implementationClass Triple store implementation class name
	 * @param array $params Triple store configuration settings
	 * @return triplestore_TripleStore Triple store implementation
	 */
	public static function getImplementation($implementationClass, &$params)
	{
		return new $implementationClass($params);
	}
	
	/**
	 * Construct a triple store object.
	 * @param array $params Triple store configuration settings
	 */
	public function __construct(&$params)
	{
		$this->username = $params['username'];
		$this->password = $params['password'];
	}
	
	/**
	 * Initialize the triple store connection.
	 */
	public function initialize()
	{
		$this->curlHandle = curl_init();
		curl_setopt($this->curlHandle, CURLOPT_RETURNTRANSFER, true);
		if($this->username !== null)
		{
			curl_setopt($this->curlHandle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
			curl_setopt($this->curlHandle, CURLOPT_USERPWD, $this->username . ':' . $this->password);
		}
	}
	
	/**
	 * Close the repository connection.
	 */
	public function shutDown()
	{
		curl_close($this->curlHandle);
	}
	
	/**
	 * Perform a SPARQL select query.
	 * @param string $query SPARQL query
	 * @return triplestore_SparqlResultSet SPARQL result set
	 * @throws triplestore_exception_QueryFailed
	 */
	public function sparqlSelect($query)
	{
		curl_setopt($this->curlHandle, CURLOPT_URL, $this->sparqlEndpointUrl);
		curl_setopt($this->curlHandle, CURLOPT_POST, true);
		curl_setopt($this->curlHandle, CURLOPT_POSTFIELDS, http_build_query(array_merge($this->sparqlSelectAdditionalParameters, array('query' => $query))));
		curl_setopt($this->curlHandle, CURLOPT_HTTPHEADER, array('Accept: ' . $this->sparqlSelectFormat));

		$result = curl_exec($this->curlHandle);
		if($result === false)
		{
			throw new triplestore_exception_QueryFailed('Query failed', null);
		}
		else if(curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) != 200)
		{
			throw new triplestore_exception_QueryFailed($result, null);
		}
		
		return new triplestore_SparqlResultSet($result, $this->sparqlSelectFormat);
	}
	
	/**
	 * Perform a SPARQL construct query.
	 * @param string $query SPARQL query
	 * @return simplerdf_RdfDocument RDF document
	 * @throws triplestore_exception_QueryFailed
	 */
	public function sparqlConstruct($query)
	{
		curl_setopt($this->curlHandle, CURLOPT_URL, $this->sparqlEndpointUrl);
		curl_setopt($this->curlHandle, CURLOPT_POST, true);
		curl_setopt($this->curlHandle, CURLOPT_POSTFIELDS, http_build_query(array_merge($this->sparqlConstructAdditionalParameters, array('query' => $query))));
		curl_setopt($this->curlHandle, CURLOPT_HTTPHEADER, array('Accept: ' . $this->sparqlConstructFormat));

		$result = curl_exec($this->curlHandle);
		if($result === false)
		{
			throw new triplestore_exception_QueryFailed('Query failed', null);
		}
		else if(curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) != 200)
		{
			throw new triplestore_exception_QueryFailed($result, null);
		}
		
		$result = new simplerdf_RdfDocument($result);
		return $result;
	}
	
	/**
	 * Perform a SPARQL ask query.
	 * @param string $query SPARQL query
	 * @return triplestore_SparqlResultSet SPARQL result set
	 * @throws triplestore_exception_QueryFailed
	 */
	public function sparqlAsk($query)
	{
		curl_setopt($this->curlHandle, CURLOPT_URL, $this->sparqlEndpointUrl);
		curl_setopt($this->curlHandle, CURLOPT_POST, true);
		curl_setopt($this->curlHandle, CURLOPT_POSTFIELDS, http_build_query(array_merge($this->sparqlAskAdditionalParameters, array('query' => $query))));
		curl_setopt($this->curlHandle, CURLOPT_HTTPHEADER, array('Accept: ' . $this->sparqlAskFormat));

		$result = curl_exec($this->curlHandle);
		if($result === false)
		{
			throw new triplestore_exception_QueryFailed('Query failed', null);
		}
		else if(curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) != 200)
		{
			throw new triplestore_exception_QueryFailed($result, null);
		}
		
		return new triplestore_SparqlResultSet($result, $this->sparqlAskFormat);
	}
	
	/**
	 * Perform a SPARQL describe query.
	 * @param string $query SPARQL query
	 * @return simplerdf_RdfDocument RDF result document
	 * @throws triplestore_exception_QueryFailed
	 */
	public function sparqlDescribe($query)
	{
		curl_setopt($this->curlHandle, CURLOPT_URL, $this->sparqlEndpointUrl);
		curl_setopt($this->curlHandle, CURLOPT_POST, true);
		curl_setopt($this->curlHandle, CURLOPT_POSTFIELDS, http_build_query(array_merge($this->sparqlDescribeAdditionalParameters, array('query' => $query))));
		curl_setopt($this->curlHandle, CURLOPT_HTTPHEADER, array('Accept: ' . $this->sparqlDescribeFormat));

		$result = curl_exec($this->curlHandle);
		if($result === false)
		{
			throw new triplestore_exception_QueryFailed('Query failed', null);
		}
		else if(curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) != 200)
		{
			throw new triplestore_exception_QueryFailed($result, null);
		}
		
		$result = new simplerdf_RdfDocument($result);
		return $result;
	}

	/**
	 * Convenience method to fetch statements matching one or more conditions.
	 * @param string $subject Subject URI
	 * @param string $predicate Predicate URI
	 * @param string $object Object URI or literal
	 * @param boolean $objectIsLiteral True if the object is a literal
	 * @param string $graphUri Graph URI
	 * @return simplerdf_RdfDocument RDF result document
	 * @throws triplestore_exception_QueryFailed
	 */
	public function fetchStatements($subject, $predicate, $object, $objectIsLiteral, $graphUri)
	{
		// generates a SPARQL construct query, can be overridden in sub classes to access the data in a more efficient triple store specific manner
		$triple = $subject === null ? '?s' : '<' . $this->sparqlEscapeUri($subject) . '>';
		$triple .= ' ';
		$triple .= $predicate === null ? '?p' : '<' . $this->sparqlEscapeUri($predicate) . '>';
		$triple .= ' ';
		if($objectIsLiteral)
		{
			$triple .= $object === null ? '?o' : '"' . $this->sparqlEscapeLiteral($object) . '"';
		}
		else
		{
			$triple .= $object === null ? '?o' : '<' . $this->sparqlEscapeUri($object) . '>';
		}
		
		if($graphUri !== null)
		{
			$query = 'construct { ' . $triple . ' } where { graph <' . $this->sparqlEscapeUri($graphUri) . '> {' . $triple . ' } }';
		}
		else
		{
			$query = 'construct { ' . $triple . ' } where { ' . $triple . ' }';
		}
		
		return $this->sparqlConstruct($query);
	}
	
	/**
	 * Escape a URI for usage in a SPARQL query (does not generate < >).
	 * @param string $uri URI
	 * @return string Escaped URI
	 */
	public function sparqlEscapeUri($uri)
	{
		// just remove '>' characters and use literal escape to filter out the rest
		return $this->sparqlEscapeLiteral(str_replace('>', '', $uri));
	}
	
	/**
	 * Escape a literal for usage in a SPARQL query (does not generate enclosing quotes).
	 * @param string $literal Literal
	 * @return string Escaped literal
	 */
	public function sparqlEscapeLiteral($literal)
	{
		// add slashes to tabs, line breaks, double quotes, quotes and slashes 
		return addcslashes($literal, "\t\n\r\"'\\");
	}
	
	/**
	 * Add / replace a graph. 
	 * @param string $graphUri Graph URI
	 * @param simplerdf_RdfDocument|simplerdf_RdfResource|string $rdf RDF data
	 */
	abstract public function replaceGraph($graphUri, $rdf);
	
	/**
	 * Remove a graph.
	 * @param string $graphUri Graph URI
	 */
	abstract public function deleteGraph($graphUri);
}